Repository navigation
[FIX] dvb: bound entry_id in CLUT parsing to prevent heap buffer overflow - #2441
Aditya-Saroha wants to merge 2 commits into
Conversation
…flow According to ETSI EN 300 743 Section 7.2.4, 2-bit CLUT entries have entry_id in range 0..3, 4-bit entries in range 0..15, and 8-bit entries in range 0..255. Previously, dvbsub_parse_clut_segment() directly used entry_id (up to 255) as an index into clut->clut4[entry_id] (4 uint32_t entries) and clut->clut16[entry_id] (16 uint32_t entries) without validating the bounds against the palette bit depth. An untrusted or malformed stream could write beyond the boundaries of clut4/clut16, corrupting adjacent heap memory (including the clut->next pointer). This commit: - Enforces strict upper bounds on entry_id for 2-bit (entry_id < 4), 4-bit (entry_id < 16), and 8-bit (entry_id < 256) CLUT entries. - Adds bounds check for CLUT segment payload size (buf_size >= 2). - Validates entry length against remaining buffer before reading compact or full_range color definitions to prevent out-of-bounds heap reads.
cfsmp3
left a comment
There was a problem hiding this comment.
This is Carlos' claude doing the first pass. Before Carlos takes a look, please address these issues.
The out-of-range index is real. A TS with a CLUT segment containing entry_id=4 with the 2-bit flag (or entry_id=255 with the 4-bit flag) gives UBSan index 4 out of bounds for type 'uint32_t [4]' at dvb_subtitle_decoder.c:1218 (and index 255 … [16] at :1223) on master. The 5 real DVB samples we have produce identical output on this branch.
- Don't drop the whole segment for one entry. In EN 300 743 the three entry-CLUT flags are independent: one entry may target several depths (e.g. flags
0xE0,entry_id10 is valid for the 4-bit and 8-bit CLUTs). Withreturn -1in thedepth & 0x80branch, such an entry now discards the rest of the CLUT definition. Please put the bound in each condition and fall through, the way FFmpeg'sdvbsubdec.cdoes, and skip only the invalid entry:if ((depth & 0x80) && entry_id < 4) { ... } else if ((depth & 0x40) && entry_id < 16) { ... } else if (depth & 0x20) { ... }
- Remove the
entry_id >= 256check.entry_idis read from one byte, so it can never be true. - Braces:
if (buf_size < 2) return -1;needs{ }. - Correct the description. The indices can't reach
clut->nextor heap metadata: the largest write (clut16[255]/ilut16[255]) still lands insidestruct DVBSubCLUT, in theclut256/ilut256arrays. ASan reports nothing on master for this input. The effect is undefined behaviour that corrupts neighbouring palette entries. Please also attach a TS sample (or a generator) instead of the in-function snippet.
Per review on PR CCExtractor#2441: - Bound entry_id within condition branches to skip invalid entries instead of dropping the segment - Delete redundant entry_id >= 256 check (entry_id is 8-bit) - Add braces to buf_size < 2 check
|
Hi Carlos, Thank you for the review. All points have been addressed in the latest commit, and the PR description has been updated:
Verified that UBSan runs completely clean on the test stream and that output on real DVB samples ( |
CCExtractor CI platform finished running the test files on linux. 172/238 tests matched the approved output:
66 tests do not match the approved output. That is the pass/fail verdict. Whether this branch caused it is a separate question, answered below.
Compared with the tip of master — test 9974, commit cccad8f:
Compared with the commit this branch was cut from — test 9878, commit 185fe3f:
No test changes behaviour relative to the tip of master: every failure above fails there too, byte for byte. The approved output for those tests is out of date, which is a baseline to review rather than a regression in this branch. |
CCExtractor CI platform finished running the test files on windows. 172/238 tests matched the approved output:
66 tests do not match the approved output. That is the pass/fail verdict. Whether this branch caused it is a separate question, answered below.
Compared with the tip of master — test 9979, commit f4519ff:
Compared with the commit this branch was cut from — test 9693, commit af4b28d:
Fail on both but produce different output — behaviour moved even though the verdict did not:
Fail there, pass here:
No test changes behaviour relative to the tip of master: every failure above fails there too, byte for byte. The approved output for those tests is out of date, which is a baseline to review rather than a regression in this branch. |
In raising this pull request, I confirm the following (please check boxes):
Reason for this PR:
Sanity check:
Repro instructions:
Reproduction
Generate a minimal Transport Stream with out-of-bounds CLUT entries using the generator script below:
Master (compiled with
-fsanitize=undefined):Branch (compiled with
-fsanitize=undefined):Runs cleanly with zero UBSan errors.
Note on AddressSanitizer (ASan): ASan reports nothing on master because
clut4[4],clut16[16], andclut256[256]are contiguous members within the heap-allocatedstruct DVBSubCLUT(1396 bytes). Out-of-bounds writes stay entirely within this allocation block, so ASan redzones are not triggered. The out-of-bounds indexing constitutes undefined behaviour that silently corrupts neighbouring palette entries, which UBSan detects immediately.Summary & Root Cause
Fixes #2440.
In
src/lib_ccx/dvb_subtitle_decoder.c:dvbsub_parse_clut_segment(),entry_id(parsed as an 8-bituint8_tfrom the stream) was used unchecked as an array index intoclut->clut4[entry_id](size 4) andclut->clut16[entry_id](size 16).According to ETSI EN 300 743 §7.2.4 (CLUT definition segment):
entry_idin range 0..3 (clut4[4],ilut4[4]).entry_idin range 0..15 (clut16[16],ilut16[16]).entry_idin range 0..255 (clut256[256],ilut256[256]).When processing an out-of-range entry (e.g.
entry_id = 4with 2-bit flag, orentry_id = 255with 4-bit flag), writes overflow the array bounds:clut4[4]lands +16 bytes intoclut16[0].clut16[255]lands +1020 bytes intoclut256.This undefined behaviour corrupts neighbouring palette entries in
struct DVBSubCLUT.Fix Details
Bound checks are placed directly inside each depth conditional branch:
Per ETSI EN 300 743 §7.2.4, an entry targeting multiple depths (e.g.
flags = 0xE0withentry_id = 10) falls through the 2-bit check and correctly updates the 4-bit and 8-bit palettes.Color fields are read and
bufis advanced (4 bytes for compact, 6 bytes for full range) ahead of the depth checks, ensuring skipped entries still consume their bytes and preserve pointer alignment for subsequent entries.entry_id >= 256check (1-byteuint8_t).{ }toif (buf_size < 2) { return -1; }and all conditional branches.Verification
dvbteletext.ts,sample_121.ts) across both--out=srt(diff -ureturns 0) and--out=spupng(diff -rreturns 0 on all generated PNG subpicture bitmaps).TS Generator Script (
gen_clut_poc.py)Click to expand standalone Python generator